// Funcalc, spreadsheet with functions
// ----------------------------------------------------------------------
// Copyright (c) 2006-2013 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Diagnostics;

namespace Corecalc.Funcalc {
  public abstract class CGExpr : CodeGenerate, IDepend {
    /// <summary>
    ///  The entry point for compilation, called from ProgramLines.  
    ///  The generated code must leave a Value on the stack top.
    /// </summary>
    public abstract void Compile();

    /// <summary>
    /// Compile expression that is expected to evaluate to a number, leaving 
    /// a float64 on the stack top and avoiding wrapping where possible.  
    /// If result is an error, produce a NaN whose 32 least significant bits
    /// give that error's int index into the ErrorValue.errorTable arraylist.
    /// </summary>
    public abstract void CompileToDoubleOrNan();

    // General version in terms of CompileToDoubleOrNan.  
    // Should be overridden in CGNumberConst, CGTextConst, CGError, CGComposite ...
    /// <summary>
    /// Compile expression that is expected to evaluate to a proper (finite 
    /// and non-NaN) number; generate code to test whether it is actually a 
    /// proper number and then execute the code generated by ifProper, or 
    /// else execute the code generated by ifOther.
    /// </summary>
    /// <param name="ifProper">Generates code for the case where the expression 
    /// evaluates to a proper number; the generated code expects to find the value as
    /// an unwrapped proper float64 on the stack top.</param>
    /// <param name="ifOther"></param>
    public virtual void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Stloc, testDouble);
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ilg.Emit(OpCodes.Call, isInfinityMethod);
      ilg.Emit(OpCodes.Brtrue, ifOther.GetLabel(ilg));
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ilg.Emit(OpCodes.Call, isNaNMethod);
      ilg.Emit(OpCodes.Brtrue, ifOther.GetLabel(ilg));
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ifProper.Generate(ilg);
      if (!ifOther.Generated) {
        Label endLabel = ilg.DefineLabel();
        ilg.Emit(OpCodes.Br, endLabel);
        ifOther.Generate(ilg);
        ilg.MarkLabel(endLabel);
      }
    }

    /// <summary>
    /// Compiles an expression as a condition, that can be true (if non-zero) or 
    /// false (if zero) or other (if +/-infinity or NaN).  If possible, avoids 
    /// computing and pushing a value and then testing it, instead performing 
    /// comparisons directly on arguments, or even statically.  This implementation 
    /// is a general version in terms of CompileToDoubleProper.  Should be overridden 
    /// in CGNumberConst, CGTextConst, CGError, CGIf, CGComparison, ...
    /// </summary>
    /// <param name="ifTrue">Generates code for the true branch</param>
    /// <param name="ifFalse">Generates code for the false branch</param>
    /// <param name="ifOther">Generates code for the other (neither true nor 
    /// false) branch</param>
    public virtual void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      CompileToDoubleProper(
        new Gen(delegate {
        ilg.Emit(OpCodes.Ldc_R8, 0.0);
        ilg.Emit(OpCodes.Beq, ifFalse.GetLabel(ilg));
        ifTrue.Generate(ilg);
        if (!ifFalse.Generated) {
          Label endLabel = ilg.DefineLabel();
          ilg.Emit(OpCodes.Br, endLabel);
          ifFalse.Generate(ilg);
          ilg.MarkLabel(endLabel);
        }
      }),
      ifOther);
    }

    /// <summary>
    /// Specialize the expression with respect to the values/residual expressions in pEnv.
    /// </summary>
    /// <param name="pEnv">Maps cell addresses to already-specialized expressions</param>
    /// <returns>The specialized expression</returns>

    public abstract CGExpr PEval(PEnv pEnv, bool hasDynamicControl);

    /// <summary>
    /// Update the evaluation conditions (in the evalConds dictionary) for every cell
    /// referenced from this expression, assuming that this expression itself 
    /// has evaluation condition evalCond.
    /// </summary>
    /// <param name="evalCond"></param>
    /// <param name="evalConds"></param>
    public abstract void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches);

    /// <summary>
    /// Called on a sheet-defined function's output 
    /// cell expression to record that that expression and some of its
    /// subexpressions are in tail position.  Only overridden in CGIf, 
    /// CGChoose and CGSdfCall (but not CGApply).
    /// </summary>
    public virtual void NoteTailPosition() { }

    /// <summary>
    /// Returns true if the expression's evaluation could 
    /// be recursive, have side effects, or take a long time.
    /// </summary>
    /// <param name="bound">The bound on non-serious expression size</param>
    public virtual bool IsSerious(ref int bound) {
      return 0 > bound--;
    }

    public abstract void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn);

    /// <summary>
    /// Update the numberUses bag with the number of number-uses of each full cell
    /// address in this expression.
    /// </summary>
    /// <param name="typ">The expected type of this expression</param>
    /// <param name="numberUses">A hashbag noting for each full cell address
    /// the number of times it is used as a NumberValue.</param>
    public abstract void CountUses(Typ typ, HashBag<FullCellAddr> numberUses);

    public abstract Typ Type();

    public bool Is(double d) {
      return this is CGNumberConst && (this as CGNumberConst).number.value == d;
    }
  }

  // ----------------------------------------------------------------

  public class CGCellRef : CGExpr {
    private readonly FullCellAddr cellAddr;
    private readonly Variable var;

    public CGCellRef(FullCellAddr cellAddr, Variable var) {
      this.cellAddr = cellAddr;
      this.var = var;
    }

    public override void Compile() {
      var.EmitLoad(ilg);
      if (var.Type == Typ.Number)
        WrapDoubleToNumberValue();
      // In other cases, there's no need to wrap the variable's contents
    }

    public override void CompileToDoubleOrNan() {
      Variable doubleVar;
      if (NumberVariables.TryGetValue(cellAddr, out doubleVar))
        doubleVar.EmitLoad(ilg);
      else {
        if (var.Type == Typ.Value) {
          var.EmitLoad(ilg);
          UnwrapToDoubleOrNan();
        } else if (var.Type == Typ.Number)
          var.EmitLoad(ilg);
        else // A variable of a type not convertible to a float64, so ArgTypeError
          LoadErrorNan(ErrorValue.argTypeError);
      }
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      return pEnv[cellAddr];
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      PathCond old;
      if (evalConds.TryGetValue(cellAddr, out old))
        evalConds[cellAddr] = old.Or(evalCond);
      else
        evalConds[cellAddr] = evalCond;
    }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) {
      dependsOn(cellAddr);
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      if (typ == Typ.Number)
        numberUses.Add(this.cellAddr);
    }

    public override string ToString() {
      return var.Name;
    }

    public override Typ Type() {
      return var.Type;
    }
  }

  // ----------------------------------------------------------------

  public class CGCachedExpr : CGExpr {
    public readonly CGExpr expr;
    private readonly CachedAtom cachedAtom;
    private LocalBuilder cacheVariable;  // The cache localvar (double) 
    private int generateCount = 0;       // Number of times emitted by CachedAtom.ToCGExpr
    private readonly int cacheNumber;    // Within the current SDF, for diagnostics only

    private const int uninitializedBits = -1;
    private static readonly double uninitializedNan = ErrorValue.MakeNan(uninitializedBits);
    private static readonly MethodInfo
      doubleToInt64BitsMethod = typeof(System.BitConverter).GetMethod("DoubleToInt64Bits");

    public CGCachedExpr(CGExpr expr, CachedAtom cachedAtom, List<CGCachedExpr> caches) {
      this.expr = expr;
      this.cachedAtom = cachedAtom;
      this.cacheNumber = caches.Count;
      caches.Add(this);
    }

    public override void Compile() {
      CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
    }

    public override void CompileToDoubleOrNan() {
      if (IsCacheNeeded)
        EmitCacheAccess();
      else
        expr.CompileToDoubleOrNan();
    }

    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      if (IsCacheNeeded)
        // Call CompileToDoubleProper via base class CompileCondition
        base.CompileCondition(ifTrue, ifFalse, ifOther);
      else
        expr.CompileCondition(ifTrue, ifFalse, ifOther);
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (IsCacheNeeded)
        // Call CompileToDoubleOrNan via base class CompileToDoubleProper
        base.CompileToDoubleProper(ifProper, ifOther);
      else
        expr.CompileToDoubleProper(ifProper, ifOther);
    }

    public void IncrementGenerateCount() {
      generateCount++;
    }

    // Cache only if used in PathCond and worth caching
    private bool IsCacheNeeded {
      get { return generateCount > 0 && !(expr is CGCellRef) && !(expr is CGConst); }
    }

    /// <summary>
    /// This must be called, at most once, before any use of the cache is generated.
    /// </summary>
    public void EmitCacheInitialization() {
      if (IsCacheNeeded) {
        Debug.Assert(cacheVariable == null);  // Has not been called before
        cacheVariable = ilg.DeclareLocal(typeof(double));
        // Console.WriteLine("Emitted {0} in localvar {1}", this, cacheVariable);
        ilg.Emit(OpCodes.Ldc_R8, uninitializedNan);
        ilg.Emit(OpCodes.Stloc, cacheVariable);
      }
    }

    /// <summary>
    /// Generate code for each use of the cache.
    /// </summary>
    private void EmitCacheAccess() {
      Debug.Assert(cacheVariable != null); // EmitCacheInitialization() has been called
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
      ilg.Emit(OpCodes.Call, isNaNMethod);
      Label endLabel = ilg.DefineLabel();
      ilg.Emit(OpCodes.Brfalse, endLabel);  // Already computed, non-NaN result
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
      ilg.Emit(OpCodes.Call, doubleToInt64BitsMethod);
      ilg.Emit(OpCodes.Conv_I4);
      ilg.Emit(OpCodes.Ldc_I4, uninitializedBits);
      ilg.Emit(OpCodes.Ceq);
      ilg.Emit(OpCodes.Brfalse, endLabel);  // Already computed, NaN result
      expr.CompileToDoubleOrNan();
      // ilg.EmitWriteLine("Filled " + this);
      ilg.Emit(OpCodes.Stloc, cacheVariable);
      ilg.MarkLabel(endLabel);
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      // Partial evaluation may encounter a cached expression in conditionals
      // etc, and should simply partially evaluate the expression inside the cache.
      // No duplication of cached expression happens in the residual program because 
      // a cached expression appears only once in the regular code, and no occurrence 
      // from evaluation conditions is added to the residual program; see ComputeCell.PEval.
      // New evaluation conditions are added later; see ProgramLines.CompileToDelegate.
      return expr.PEval(pEnv, hasDynamicControl);
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      throw new ImpossibleException("CGCachedExpr.EvalCond");
    }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) {
      expr.DependsOn(here, dependsOn);
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      expr.CountUses(typ, numberUses);
    }

    public override string ToString() {
      return "CACHE#" + cacheNumber + "[" + expr + "]";
    }

    public override Typ Type() {
      return Typ.Number;
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A CGComposite represents an arithmetic operation, a call to a 
  /// built-in "function" (including IF, AND, OR, RAND, LN, EXP, ...) 
  /// a call to a sheet-defined or external function, and more.
  /// </summary>
  public abstract class CGComposite : CGExpr {
    protected readonly CGExpr[] es;

    protected CGComposite(CGExpr[] es) {
      this.es = es;
    }

    public static CGExpr Make(String name, CGExpr[] es) {
      name = name.ToUpper();
      // This switch should agree with the function table in Functions.cs
      switch (name) {
        case "+": return new CGArithmetic2(OpCodes.Add, name, es);
        case "*": return new CGArithmetic2(OpCodes.Mul, name, es);
        case "-": return new CGArithmetic2(OpCodes.Sub, name, es);
        case "/": return new CGArithmetic2(OpCodes.Div, name, es);

        case "=":  return CGEqual.Make(es);
        case "<>": return CGNotEqual.Make(es);
        case ">":  return new CGGreaterThan(es);
        case "<=": return new CGLessThanOrEqual(es);
        case "<":  return new CGLessThan(es);
        case ">=": return new CGGreaterThanOrEqual(es);

        // Non-strict, or other special treatment
        case "AND": return new CGAnd(es);
        case "APPLY": return new CGApply(es);
        case "CHOOSE": return new CGChoose(es);
        case "CLOSURE": return new CGClosure(es);
        case "ERR": return new CGError(es);
        case "EXTERN": return new CGExtern(es);
        case "IF": return new CGIf(es);
        case "NA":
          if (es.Length == 0)
            return new CGError(ErrorValue.naError);
          else
            return new CGError(ErrorValue.argCountError);
        case "NEG": return new CGNeg(es);
        case "NOT": return new CGNot(es);
        case "OR": return new CGOr(es);
        case "PI":
          if (es.Length == 0)
            return new CGNumberConst(NumberValue.PI);
          else
            return new CGError(ErrorValue.argCountError);
        case "VOLATILIZE": 
          if (es.Length == 1)
            return es[0];
          else
            return new CGError(ErrorValue.argCountError);
        default:
          // The general case for most built-in functions with unspecific argument types
          FunctionInfo functionInfo;
          if (FunctionInfo.Find(name, out functionInfo))
            return new CGFunctionCall(functionInfo, es);
          else { // May be a sheet-defined function
            SdfInfo sdfInfo = SdfManager.GetInfo(name);
            if (sdfInfo != null)
              return new CGSdfCall(sdfInfo, es);
            else
              return new CGError(ErrorValue.nameError);
          }
      }
    }

    protected abstract Typ GetInputTypWithoutLengthCheck(int pos);

    public Typ GetInputTyp(int pos) {
      if (0 <= pos && pos < Arity)
        return GetInputTypWithoutLengthCheck(pos);
      else
        return Typ.Value;
    }

    public CGExpr[] PEvalArgs(PEnv pEnv, CGExpr r0, bool hasDynamicControl) {
      CGExpr[] res = new CGExpr[es.Length];
      res[0] = r0;
      for (int i = 1; i < es.Length; i++)
        res[i] = es[i].PEval(pEnv, hasDynamicControl);
      return res;
    }

    public override bool IsSerious(ref int bound) {
      bool serious = base.IsSerious(ref bound);
      for (int i = 0; !serious && i < es.Length; i++)
        serious = es[i].IsSerious(ref bound);
      return serious;
    }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) {
      foreach (CGExpr e in es)
        e.DependsOn(here, dependsOn);
    }

    protected String FormatAsCall(String name) {
      return FunctionValue.FormatAsCall(name, es);
    }

    public abstract int Arity { get; }
  }

  // ----------------------------------------------------------------

  public class CGIf : CGComposite {
    public CGIf(CGExpr[] es) : base(es) { }

    // This handles compilation of IF(e0,e1,e2) in a Value context
    public override void Compile() {
      if (es.Length != 3)
        LoadErrorValue(ErrorValue.argCountError);
      else
        es[0].CompileCondition(
          new Gen(delegate { es[1].Compile(); }),
          new Gen(delegate { es[2].Compile(); }),
          new Gen(delegate {
          ilg.Emit(OpCodes.Ldloc, testDouble);
          WrapDoubleToNumberValue();
        }));
    }

    // This handles compilation of 5 + IF(e0,e1,e2) and such
    public override void CompileToDoubleOrNan() {
      if (es.Length != 3)
        LoadErrorValue(ErrorValue.argCountError);
      else
        es[0].CompileCondition(
          new Gen(delegate { es[1].CompileToDoubleOrNan(); }),
          new Gen(delegate { es[2].CompileToDoubleOrNan(); }),
          new Gen(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    // This handles compilation of 5 > IF(e0,e1,e2) and such
    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (es.Length != 3) {
        SetArgCountErrorNan();
        ifOther.Generate(ilg);
      } else
        es[0].CompileCondition(
          new Gen(delegate { es[1].CompileToDoubleProper(ifProper, ifOther); }),
          new Gen(delegate { es[2].CompileToDoubleProper(ifProper, ifOther); }),
          ifOther);
    }

    // This handles compilation of IF(IF(e00,e01,e02), e1, e2) and such
    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      if (es.Length != 3) {
        SetArgCountErrorNan();
        ifOther.Generate(ilg);
      } else
        es[0].CompileCondition(
        new Gen(delegate { es[1].CompileCondition(ifTrue, ifFalse, ifOther); }),
        new Gen(delegate { es[2].CompileCondition(ifTrue, ifFalse, ifOther); }),
        ifOther);
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      CGExpr r0 = es[0].PEval(pEnv, hasDynamicControl);
      if (r0 is CGNumberConst) {
        if ((r0 as CGNumberConst).number.value != 0.0)
          return es[1].PEval(pEnv, hasDynamicControl);
        else
          return es[2].PEval(pEnv, hasDynamicControl);
      } else
        return new CGIf(PEvalArgs(pEnv, r0, true));
    }

    public override string ToString() {
      return FormatAsCall("IF");
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      if (es.Length == 3) {
        CachedAtom atom = new CachedAtom(es[0], caches);
        es[0].EvalCond(evalCond, evalConds, caches);
        es[0] = atom.cachedExpr;
        es[1].EvalCond(evalCond.And(atom), evalConds, caches);
        es[2].EvalCond(evalCond.AndNot(atom), evalConds, caches);
      }
    }

    public override void NoteTailPosition() {
      if (es.Length == 3) {
        es[1].NoteTailPosition();
        es[2].NoteTailPosition();
      }
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      if (es.Length == 3) {
        es[0].CountUses(Typ.Number, numberUses);
        es[1].CountUses(typ, numberUses);
        es[2].CountUses(typ, numberUses);
      }
    }

    public override Typ Type() {
      if (es.Length != 3)
        return Typ.Error;
      else
        return Lub(es[1].Type(), es[2].Type());
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      if (pos == 0)
        return Typ.Number;
      else
        return Typ.Value;
    }

    public override int Arity { get { return 3; } }
  }

  // ----------------------------------------------------------------

  public class CGChoose : CGComposite {
    public CGChoose(CGExpr[] es) : base(es) { }

    public override void Compile() {
      es[0].CompileToDoubleProper(new Gen(delegate {
        Label endLabel = ilg.DefineLabel();
        Label[] labels = new Label[es.Length - 1];
        for (int i = 1; i < es.Length; i++)
          labels[i - 1] = ilg.DefineLabel();
        ilg.Emit(OpCodes.Conv_I4);
        ilg.Emit(OpCodes.Ldc_I4, 1);
        ilg.Emit(OpCodes.Sub);
        ilg.Emit(OpCodes.Switch, labels);
        LoadErrorValue(ErrorValue.valueError);
        for (int i = 1; i < es.Length; i++) {
          ilg.Emit(OpCodes.Br, endLabel);
          ilg.MarkLabel(labels[i - 1]);
          es[i].Compile();
        }
        ilg.MarkLabel(endLabel);
      }),
      GenLoadErrorValue(ErrorValue.argTypeError)
      );
    }

    public override void CompileToDoubleOrNan() {
      es[0].CompileToDoubleProper(new Gen(delegate {
        Label endLabel = ilg.DefineLabel();
        Label[] labels = new Label[es.Length - 1];
        for (int i = 1; i < es.Length; i++)
          labels[i - 1] = ilg.DefineLabel();
        ilg.Emit(OpCodes.Conv_I4);
        ilg.Emit(OpCodes.Ldc_I4, 1);
        ilg.Emit(OpCodes.Sub);
        ilg.Emit(OpCodes.Switch, labels);
        LoadErrorNan(ErrorValue.valueError);
        for (int i = 1; i < es.Length; i++) {
          ilg.Emit(OpCodes.Br, endLabel);
          ilg.MarkLabel(labels[i - 1]);
          es[i].CompileToDoubleOrNan();
        }
        ilg.MarkLabel(endLabel);
      }),
      GenLoadErrorNan(ErrorValue.argTypeError)
      );
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      CGExpr r0 = es[0].PEval(pEnv, hasDynamicControl);
      if (r0 is CGNumberConst) {
        int index = (int)((r0 as CGNumberConst).number.value);
        if (index < 1 || index >= es.Length)
          return new CGError(ErrorValue.valueError);
        else
          return es[index].PEval(pEnv, hasDynamicControl);
      } else
        return new CGChoose(PEvalArgs(pEnv, r0, true /* has dynamic control */));
    }

    public override string ToString() {
      return FormatAsCall("CHOOSE");
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      if (es.Length >= 1) {
        CachedAtom atom = new CachedAtom(es[0], caches);
        CGCachedExpr cached = atom.cachedExpr;
        es[0].EvalCond(evalCond, evalConds, caches);
        es[0] = cached;
        for (int i = 1; i < es.Length; i++) {
          CGExpr iConst = CGConst.Make(i);
          CGExpr cond = new CGEqual(new CGExpr[] { cached, iConst });
          es[i].EvalCond(evalCond.And(new CachedAtom(cond, caches)), evalConds, caches);
        }
      }
    }

    public override void NoteTailPosition() {
      for (int i = 1; i < es.Length; i++)
        es[i].NoteTailPosition();
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      es[0].CountUses(Typ.Number, numberUses);
      for (int i = 1; i < es.Length; i++)
        es[i].CountUses(typ, numberUses);
    }

    public override Typ Type() {
      Typ result = Typ.Error;
      for (int i = 1; i < es.Length; i++)
        result = Lub(result, es[i].Type());
      return result;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      if (pos == 0)
        return Typ.Number;
      else
        return Typ.Value;
    }

    public override int Arity { get { return es.Length; } }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A CGAnd object represents a variadic AND operation, strict only
  /// in its first argument -- although that's not Excel semantics.
  /// </summary>
  public class CGAnd : CGComposite {
    public CGAnd(CGExpr[] es) : base(es) { }

    public override void Compile() {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField); }),
        GenLoadTestDoubleErrorValue()
        );
    }

    public override void CompileToDoubleOrNan() {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); ifProper.Generate(ilg); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); ifProper.Generate(ilg); }),
        ifOther);
    }

    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      for (int i = es.Length - 1; i >= 0; i--) {
        // These declarations are needed to capture rvalues rather than lvalues:
        CGExpr ei = es[i];
        Gen localIfTrue = ifTrue;
        ifTrue = new Gen(delegate { ei.CompileCondition(localIfTrue, ifFalse, ifOther); });
      }
      ifTrue.Generate(ilg);
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      List<CGExpr> res = new List<CGExpr>();
      for (int i = 0; i < es.Length; i++) {
        CGExpr ri = es[i].PEval(pEnv, hasDynamicControl || res.Count > 0);
        if (ri is CGNumberConst) {
          // A FALSE operand makes the AND false; a TRUE operand can be ignored
          if ((ri as CGNumberConst).number.value == 0.0)
            return new CGNumberConst(NumberValue.ZERO);
        } else
          res.Add(ri);
      }
      // The residual AND consists of the non-constant operands, if any
      if (res.Count == 0)
        return new CGNumberConst(NumberValue.ONE);
      else
        return new CGAnd(res.ToArray());
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      for (int i = 0; i < es.Length; i++) {
        es[i].EvalCond(evalCond, evalConds, caches);
        if (SHORTCIRCUIT_EVALCONDS && i != es.Length - 1) {
          // Take short-circuit evaluation into account for precision
          CachedAtom atom = new CachedAtom(es[i], caches);
          evalCond = evalCond.And(atom);
          es[i] = atom.cachedExpr;
        }
      }
    }

    public override string ToString() {
      return FormatAsCall("AND");
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      foreach (CGExpr e in es)
        e.CountUses(Typ.Number, numberUses);
    }

    public override Typ Type() {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Number;
    }

    public override int Arity { get { return es.Length; } }
  }

  // ----------------------------------------------------------------

  public class CGOr : CGComposite {
    public CGOr(CGExpr[] es) : base(es) { }

    public override void Compile() {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField); }),
        GenLoadTestDoubleErrorValue()
        );
    }

    public override void CompileToDoubleOrNan() {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      CompileCondition(
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); ifProper.Generate(ilg); }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); ifProper.Generate(ilg); }),
        ifOther);
    }

    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      for (int i = es.Length - 1; i >= 0; i--) {
        // These declarations are needed to capture rvalues rather than lvalues:
        CGExpr ei = es[i];
        Gen localIfFalse = ifFalse;
        ifFalse = new Gen(delegate { ei.CompileCondition(ifTrue, localIfFalse, ifOther); });
      }
      ifFalse.Generate(ilg);
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      List<CGExpr> res = new List<CGExpr>();
      for (int i = 0; i < es.Length; i++) {
        CGExpr ri = es[i].PEval(pEnv, hasDynamicControl || res.Count > 0);
        if (ri is CGNumberConst) {
          // A TRUE operand makes the OR true; a FALSE operand can be ignored
          if ((ri as CGNumberConst).number.value != 0.0)
            return new CGNumberConst(NumberValue.ONE);
        } else
          res.Add(ri);
      }
      // The residual OR consists of the non-constant operands, if any
      if (res.Count == 0)
        return new CGNumberConst(NumberValue.ZERO);
      else
        return new CGOr(res.ToArray());
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      for (int i = 0; i < es.Length; i++) {
        es[i].EvalCond(evalCond, evalConds, caches);
        if (SHORTCIRCUIT_EVALCONDS && i != es.Length - 1) {
          // Take short-circuit evaluation into account for precision
          CachedAtom atom = new CachedAtom(es[i], caches);
          evalCond = evalCond.AndNot(atom);
          es[i] = atom.cachedExpr;
        }
      }
    }

    public override string ToString() {
      return FormatAsCall("OR");
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      foreach (CGExpr e in es)
        e.CountUses(Typ.Number, numberUses);
    }

    public override Typ Type() {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Number;
    }

    public override int Arity { get { return es.Length; } }
  }

  // ----------------------------------------------------------------

  public abstract class CGStrictOperation : CGComposite {
    // For partial evaluation; null for CGApply, CGExtern, CGSdfCall:
    public readonly Applier applier; 

    public CGStrictOperation(CGExpr[] es, Applier applier)
      : base(es) {
      this.applier = applier;
    }

    public CGExpr[] PEvalArgs(PEnv pEnv, bool hasDynamicControl) {
      CGExpr[] res = new CGExpr[es.Length];
      for (int i = 0; i < es.Length; i++)
        res[i] = es[i].PEval(pEnv, hasDynamicControl);
      return res;
    }

    public bool AllConstant(CGExpr[] res) {
      for (int i = 0; i < res.Length; i++)
        if (!(res[i] is CGConst))
          return false;
      return true;
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      CGExpr[] res = PEvalArgs(pEnv, hasDynamicControl);
      // If all args are constant then evaluate else residualize:
      if (AllConstant(res)) {
        Expr[] es = new Expr[res.Length];
        for (int i = 0; i < res.Length; i++)
          es[i] = Const.Make((res[i] as CGConst).Value);
        // Use the interpretive implementation's applier on a fake sheet 
        // and fake cell coordinates, but constant argument expressions:
        return CGConst.Make(applier(null, es, -1, -1));
      } else
        return Residualize(res);
    }

    public abstract CGExpr Residualize(CGExpr[] res);

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) {
      for (int i = 0; i < es.Length; i++)
        es[i].EvalCond(evalCond, evalConds, caches);
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) {
      for (int i = 0; i < es.Length; i++)
        es[i].CountUses(GetInputTyp(i), numberUses);
    }
  }

  // ---------------------------------------------------------------

  /// <summary>
  /// Call to a fixed-arity or variable-arity strict built-in function
  /// </summary>
  public class CGFunctionCall : CGStrictOperation {
    protected readonly FunctionInfo functionInfo;

    public CGFunctionCall(FunctionInfo functionInfo, CGExpr[] es)
      : base(es, functionInfo.applier) {
      this.functionInfo = functionInfo;
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      // Volatile functions must be residualized
      if (functionInfo.name == "NOW" || functionInfo.name == "RAND")
        return Residualize(PEvalArgs(pEnv, hasDynamicControl));
      else
        return base.PEval(pEnv, hasDynamicControl);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGFunctionCall(functionInfo, res);
    }

    public override void Compile() {
      Gen success =
          new Gen(delegate {
            ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
            if (functionInfo.signature.retType == Typ.Number)
              WrapDoubleToNumberValue();
          });
      if (Arity < 0) { // Variable arity
        CompileToValueArray(es.Length, 0, es);
        success.Generate(ilg);
      } else if (es.Length != Arity)
        LoadErrorValue(ErrorValue.argCountError);
      else {
        // TODO: ifOther should probably load error from testValue instead?
        Gen ifOther = GenLoadErrorValue(ErrorValue.argTypeError);
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    public override void CompileToDoubleOrNan() {
      Gen success =
          new Gen(delegate {
            ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
            if (functionInfo.signature.retType != Typ.Number)
              UnwrapToDoubleOrNan();
          });
      if (Arity < 0) { // Variable arity 
        CompileToValueArray(es.Length, 0, es);
        success.Generate(ilg);
      } else if (es.Length != Arity)
        LoadErrorNan(ErrorValue.argCountError);
      else {
        // TODO: ifOther should probably load error from testValue instead?
        Gen ifOther = GenLoadErrorNan(ErrorValue.argTypeError);
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    // Special case for the argumentless and always-proper 
    // functions RAND and NOW, often used in conditions
    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (es.Length == 0 && (functionInfo.name == "RAND" || functionInfo.name == "NOW")) {
        ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
        ifProper.Generate(ilg);
      } else
        base.CompileToDoubleProper(ifProper, ifOther);
    }

    // Generate code to evaluate all argument expressions, including the receiver es[1]
    // if the method is an instance method, and convert their values to .NET types.

    private void CompileArgumentsAndApply(CGExpr[] es, Gen ifSuccess, Gen ifOther) {
      int argCount = es.Length;
      // The error continuations must pop the arguments computed so far.
      Gen[] errorCont = new Gen[argCount];
      if (argCount > 0)
        errorCont[0] = ifOther;
      for (int i = 1; i < argCount; i++) {
        int ii = i; // Capture lvalue -- do NOT inline!
        errorCont[ii] = new Gen(delegate {
          ilg.Emit(OpCodes.Pop);
          errorCont[ii - 1].Generate(ilg);
        });
      }
      // Generate code, backwards, to evaluate argument expressions and
      // convert to the .NET method's argument types
      for (int i = argCount - 1; i >= 0; i--) {
        // These local vars capture rvalue rather than lvalue -- do NOT inline them!
        CGExpr ei = es[i];
        Gen localSuccess = ifSuccess;
        Typ argType = functionInfo.signature.argTypes[i];
        Gen ifError = errorCont[i];
        if (argType == Typ.Number)
          ifSuccess = new Gen(delegate {
               ei.CompileToDoubleOrNan();
               localSuccess.Generate(ilg);
             });
        else if (argType == Typ.Function)
          ifSuccess = new Gen(delegate {
               ei.Compile();
               CheckType(FunctionValue.type, localSuccess, ifError);
             });
        else if (argType == Typ.Array)
          ifSuccess = new Gen(delegate {
               ei.Compile();
               CheckType(ArrayValue.type, localSuccess, ifError);
             });
        else if (argType == Typ.Text)
          ifSuccess = new Gen(delegate {
              ei.Compile();
              CheckType(TextValue.type, localSuccess, ifError);
            });
        else // argType.Value -- TODO: neglects to propagate ErrorValue from argument
          ifSuccess = new Gen(delegate {
              ei.Compile();
              localSuccess.Generate(ilg);
            });
      }
      ifSuccess.Generate(ilg);
    }

    public override bool IsSerious(ref int bound) {
      return functionInfo.isSerious || base.IsSerious(ref bound);
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Arity < 0 ? Typ.Value : functionInfo.signature.argTypes[pos];
    }

    public override Typ Type() {
      return functionInfo.signature.retType;
    }

    public override int Arity {
      get { return functionInfo.signature.Arity; }
    }

    public override string ToString() {
      return FormatAsCall(functionInfo.name);
    }
  }

  // ----------------------------------------------------------------

  public class CGSdfCall : CGStrictOperation {
    private readonly SdfInfo sdfInfo;
    private bool isInTailPosition = false;

    public CGSdfCall(SdfInfo sdfInfo, CGExpr[] es)
      : base(es, null) {
      this.sdfInfo = sdfInfo;
    }

    public override void Compile() {
      if (es.Length != sdfInfo.arity)
        LoadErrorValue(ErrorValue.argCountError);
      else {
        ilg.Emit(OpCodes.Ldsfld, SdfManager.sdfDelegatesField);
        ilg.Emit(OpCodes.Ldc_I4, sdfInfo.index);
        ilg.Emit(OpCodes.Ldelem_Ref);
        ilg.Emit(OpCodes.Castclass, sdfInfo.MyType);
        for (int i = 0; i < es.Length; i++)
          es[i].Compile();
        if (isInTailPosition)
          ilg.Emit(OpCodes.Tailcall);
        ilg.Emit(OpCodes.Call, sdfInfo.MyInvoke);
        if (isInTailPosition)
          ilg.Emit(OpCodes.Ret);
      }
    }

    public override void CompileToDoubleOrNan() {
      Compile();
      UnwrapToDoubleOrNan();
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      CGExpr[] res = PEvalArgs(pEnv, hasDynamicControl);
      // The static argument positions are those that have args[i] != naError
      Value[] args = new Value[res.Length];
      bool anyStatic = false, allStatic = true;
      for (int i = 0; i < res.Length; i++)
        if (res[i] is CGConst) {
          args[i] = (res[i] as CGConst).Value;
          anyStatic = true;
        } else {
          args[i] = ErrorValue.naError; // Generalize to dynamic
          allStatic = false;
        }
      if (!hasDynamicControl) {
        // This would be wrong, because the called function might contain
        // volatile functions and in that case should residualize:
        // if (allStatic)       // If all arguments static, just call the SDF
        //  return CGConst.Make(sdfInfo.Apply(args));
        // else 
        if (anyStatic)  // Specialize if there are static arguments
          return Specialize(res, args);
        // Do nothing if all arguments are dynamic
      } else {
        // If under dynamic control reduce to longest static prefix
        // where the argument values agree.
        // TODO: This is wrong -- should always specialize when the call is not 
        // recursive
        ICollection<Value[]> pending = SdfManager.PendingSpecializations(sdfInfo.name);
        Value[] maxArray = null;
        int maxCount = 0;
        foreach (Value[] vs in pending) {
          int agree = AgreeCount(vs, args);
          if (agree > maxCount) {
            maxCount = agree;
            maxArray = vs;
          }
        }
        if (maxCount > 0) {
          SetNaInArgs(maxArray, args);
          return Specialize(res, args);
        }
      }
      return new CGSdfCall(sdfInfo, res);
    }

    private static int AgreeCount(Value[] xs, Value[] ys) {
      Debug.Assert(xs.Length == ys.Length);
      int count = 0;
      for (int i = 0; i < xs.Length; i++)
        if (xs[i] != ErrorValue.naError && ys[i] != ErrorValue.naError && xs[i].Equals(ys[i]))
          count++;
      return count;
    }

    private static void SetNaInArgs(Value[] xs, Value[] args) {
      Debug.Assert(xs.Length == args.Length);
      for (int i = 0; i < xs.Length; i++)
        if (!xs[i].Equals(args[i]))
          args[i] = ErrorValue.naError; // Generalize to dynamic
    }

    private CGSdfCall Specialize(CGExpr[] res, Value[] args) {
      FunctionValue fv = new FunctionValue(sdfInfo, args);
      SdfInfo residualSdf = SdfManager.SpecializeAndCompile(fv);
      CGExpr[] residualArgs = new CGExpr[fv.Arity];
      int j = 0;
      for (int i = 0; i < args.Length; i++)
        if (args[i] == ErrorValue.naError)
          residualArgs[j++] = res[i];
      return new CGSdfCall(residualSdf, residualArgs);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      throw new ImpossibleException("CGSdfCall.Residualize");
    }

    public override bool IsSerious(ref int bound) {
      return true;
    }

    public override string ToString() {
      return FormatAsCall(sdfInfo.name);
    }

    public override void NoteTailPosition() {
      isInTailPosition = true;
    }

    public override Typ Type() {
      return Typ.Value;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Value;
    }

    public override int Arity { get { return sdfInfo.arity; } }
  }

  // ----------------------------------------------------------------

  class CGApply : CGStrictOperation {
    public CGApply(CGExpr[] es) : base(es, null) { }

    public override void Compile() {
      if (es.Length < 1)
        LoadErrorValue(ErrorValue.argCountError);
      else {
        es[0].Compile();
        CheckType(FunctionValue.type,
          new Gen(delegate {
            int arity = es.Length - 1;
            // Don't check arity here; it is done elsewhere
            // Compute and push additional arguments
            for (int i = 1; i < es.Length; i++)
              es[i].Compile();
            // Call the appropriate CallN method on the FunctionValue
            ilg.Emit(OpCodes.Call, FunctionValue.callMethods[arity]);
          }),
        GenLoadErrorValue(ErrorValue.argTypeError));
      }
    }

    public override void CompileToDoubleOrNan() {
      Compile();
      UnwrapToDoubleOrNan();
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      // When FunctionValue is known, reduce to a CGSdfCall node.  
      // Don't actually call the function (even on constant arguments); could loop.
      CGExpr[] res = PEvalArgs(pEnv, hasDynamicControl);
      if (res[0] is CGValueConst) {
        FunctionValue fv = (res[0] as CGValueConst).Value as FunctionValue;
        if (fv != null) {
          CGExpr[] args = new CGExpr[fv.args.Length];
          int j = 1;
          for (int i = 0; i < args.Length; i++)
            if (fv.args[i] != ErrorValue.naError)
              args[i] = CGConst.Make(fv.args[i]);
            else
              args[i] = res[j++];
          return new CGSdfCall(fv.sdfInfo, args);
        } else
          return new CGError(ErrorValue.argCountError);
      } else
        return new CGApply(res);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      throw new ImpossibleException("CGApply.Residualize");
    }

    public override bool IsSerious(ref int bound) {
      return true;
    }

    public override string ToString() {
      return FormatAsCall("APPLY");
    }

    public override void NoteTailPosition() {
      // We currently do not try to optimize tail calls in APPLY
      // isInTailPosition = true;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      switch (pos) {
        // Could infer the expected argument types for a more precise function type:
        case 0: return Typ.Function;
        default: return Typ.Value;
      }
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type() {
      // An SDF in general returns a Value
      return Typ.Value;
    }
  }

  // ----------------------------------------------------------------

  class CGExtern : CGStrictOperation {
    // If ef==null then errorValue is set to the error that occurred 
    // during lookup, and the other fields are invalid
    private readonly ExternalFunction ef;
    private readonly ErrorValue errorValue;
    private readonly Typ resType;
    private readonly Typ[] argTypes;

    private static readonly ISet<Type>
      signed32 = new HashSet<Type>(),
      unsigned32 = new HashSet<Type>(),
      numeric = new HashSet<Type>();

    static CGExtern() {
      signed32.Add(typeof(System.Int32));
      signed32.Add(typeof(System.Int16));
      signed32.Add(typeof(System.SByte));
      unsigned32.Add(typeof(System.UInt32));
      unsigned32.Add(typeof(System.UInt16));
      unsigned32.Add(typeof(System.Byte));
      numeric.Add(typeof(System.Double));
      numeric.Add(typeof(System.Single));
      numeric.Add(typeof(System.Int64));
      numeric.Add(typeof(System.UInt64));
      numeric.Add(typeof(System.Boolean));
      numeric.UnionWith(signed32);
      numeric.UnionWith(unsigned32);
    }

    public CGExtern(CGExpr[] es)
      : base(es, null) {
      if (es.Length < 1)
        errorValue = ErrorValue.argCountError;
      else {
        CGTextConst nameAndSignatureConst = es[0] as CGTextConst;
        if (nameAndSignatureConst == null)
          errorValue = ErrorValue.argTypeError;
        else {
          try {
            // This retrieves the method from cache, or creates it:
            ef = ExternalFunction.Make(nameAndSignatureConst.value.value);
            if (ef.arity != es.Length - 1) {
              ef = null;
              errorValue = ErrorValue.argCountError;
            } else {
              resType = FromType(ef.ResType);
              argTypes = new Typ[ef.arity];
              for (int i = 0; i < argTypes.Length; i++)
                argTypes[i] = FromType(ef.ArgType(i));
            }
          } catch (Exception exn)  // Covers a multitude of sins
          {
            errorValue = ErrorValue.Make(exn.Message);
          }
        }
      }
    }

    private static Typ FromType(Type t) {
      if (numeric.Contains(t))
        return Typ.Number;
      else if (t == typeof(System.String))
        return Typ.Text;
      else
        return Typ.Value;
    }

    public override void Compile() {
      if (ef == null)
        LoadErrorValue(errorValue);
      else {
        // If argument evaluation is successful, call the external function 
        // and convert its result to Value; if unsuccessful, return ArgTypeError
        Gen success;
        // First some return type special cases, avoid boxing:
        if (ef.ResType == typeof(System.Double))
          success = new Gen(delegate {
            ef.EmitCall(ilg);
            ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
          });
        else if (numeric.Contains(ef.ResType))
          success = new Gen(delegate {
            ef.EmitCall(ilg);
            ilg.Emit(OpCodes.Conv_R8);
            ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
          });
        else if (ef.ResType == typeof(char))
          success = new Gen(delegate {
            ef.EmitCall(ilg);
            ilg.Emit(OpCodes.Call, TextValue.fromNakedCharMethod);
          });          
        else if (ef.ResType == typeof(void))
          success = new Gen(delegate {
              ef.EmitCall(ilg);
              ilg.Emit(OpCodes.Ldsfld, TextValue.voidField);
          });
        else
          success = new Gen(delegate {
            ef.EmitCall(ilg);
            if (ef.ResType.IsValueType)
              ilg.Emit(OpCodes.Box, ef.ResType);
            ilg.Emit(OpCodes.Call, ef.ResConverter.Method);
          });
        Gen ifOther = GenLoadErrorValue(ErrorValue.argTypeError);
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    public override void CompileToDoubleOrNan() {
      if (ef == null)
        ilg.Emit(OpCodes.Ldc_R8, errorValue.ErrorNan);
      else {
        // sestoft: This is maybe correct
        Gen ifOther = GenLoadErrorNan(ErrorValue.argTypeError);
        if (ef.ResType == typeof(System.Double)) {
          // If argument evaluation is successful, call external function 
          // and continue with ifDouble; otherwise continue with ifOther
          Gen success = new Gen(delegate { ef.EmitCall(ilg); });
          CompileArgumentsAndApply(es, success, ifOther);
        } else if (numeric.Contains(ef.ResType)) {
          // If argument evaluation is successful, call external function, convert 
          // to float64
          Gen success =
            new Gen(delegate {
              ef.EmitCall(ilg);
              ilg.Emit(OpCodes.Conv_R8);
            });
          CompileArgumentsAndApply(es, success, ifOther);
        } else // Result type cannot be converted to a float64
          ifOther.Generate(ilg);
      }
    }

    // Generate code to evaluate all argument expressions, including the receiver es[1]
    // if the method is an instance method, and convert their values to .NET types.

    private void CompileArgumentsAndApply(CGExpr[] es, Gen ifSuccess, Gen ifOther) {
      int argCount = es.Length - 1;
      // The error continuations must pop the arguments computed so far:
      Gen[] errorCont = new Gen[argCount];
      if (argCount > 0)
        errorCont[0] = ifOther;
      for (int i = 1; i < argCount; i++) {
        int ii = i; // Capture lvalue -- do NOT inline!
        errorCont[ii] = new Gen(delegate {
          ilg.Emit(OpCodes.Pop);
          errorCont[ii - 1].Generate(ilg);
        });
      }
      // Generate code, backwards, to evaluate argument expressions and
      // convert to external method's argument types
      for (int i = argCount - 1; i >= 0; i--) {
        // These local vars capture rvalue rather than lvalue -- do NOT inline them!
        CGExpr ei = es[i + 1];
        Gen localSuccess = ifSuccess;
        int argIndex = i;
        Type argType = ef.ArgType(i);
        Gen ifError = errorCont[i];
        // First some special cases to avoid boxing:
        if (argType == typeof(System.Double))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleOrNan();
               localSuccess.Generate(ilg);
             });
        else if (argType == typeof(System.Single))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleOrNan();
               ilg.Emit(OpCodes.Conv_R4);
               localSuccess.Generate(ilg);
             });
        else if (signed32.Contains(argType))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleProper(
                 new Gen(delegate {
                 ilg.Emit(OpCodes.Conv_I4);
                 localSuccess.Generate(ilg);
               }),
                 ifError);
             });
        else if (unsigned32.Contains(argType))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleProper(
                 new Gen(delegate {
                 ilg.Emit(OpCodes.Conv_U4);
                 localSuccess.Generate(ilg);
               }),
                 ifError);
             });
        else if (argType == typeof(System.Int64))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleProper(
                 new Gen(delegate {
                 ilg.Emit(OpCodes.Conv_I8);
                 localSuccess.Generate(ilg);
               }),
                 ifError);
             });
        else if (argType == typeof(System.UInt64))
          ifSuccess = new Gen(
             delegate {
               ei.CompileToDoubleProper(
                 new Gen(delegate {
                  ilg.Emit(OpCodes.Conv_U8);
                  localSuccess.Generate(ilg);
               }),
                 ifError);
             });
        else if (argType == typeof(System.Boolean))
          ifSuccess = new Gen(
              delegate {
                ei.CompileToDoubleProper(
                   new Gen(delegate {
                    ilg.Emit(OpCodes.Ldc_R8, 0.0);
                    ilg.Emit(OpCodes.Ceq);
                    localSuccess.Generate(ilg);
                }),
                   ifError);
              });
        else if (argType == typeof(System.Char))
          ifSuccess = new Gen(
              delegate {
                ei.Compile();
                ilg.Emit(OpCodes.Call, TextValue.toNakedCharMethod);
                localSuccess.Generate(ilg);
              });
        else if (argType == typeof(System.String))
          ifSuccess = new Gen(
            delegate {
              ei.Compile();
              UnwrapToString(localSuccess, ifError);
            });
        else // General cases: String[], double[], double[,], ...
          ifSuccess = new Gen(
            delegate {
              ei.Compile();
              ilg.Emit(OpCodes.Call, ef.ArgConverter(argIndex).Method);
              if (argType.IsValueType)  // must unbox wrapped value type, but this is too simple-minded
                ilg.Emit(OpCodes.Unbox, argType);             
              localSuccess.Generate(ilg);
            });
      }
      ifSuccess.Generate(ilg);
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      // Always residualize; external function could have effects or be volatile
      return new CGExtern(PEvalArgs(pEnv, hasDynamicControl));
    }

    public override CGExpr Residualize(CGExpr[] res) {
      throw new ImpossibleException("CGExtern.Residualize");
    }

    public override bool IsSerious(ref int bound) {
      return true;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      if (ef == null)
        return Typ.Value;
      switch (pos) {
        case 0:
          return Typ.Text;
        default:
          return argTypes[pos - 1];
      }
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type() {
      if (ef == null)
        return Typ.Value;
      else
        return resType;  // From external function signature
    }
  }

  // ----------------------------------------------------------------

  public class CGNormalCellArea : CGExpr {
    private readonly int index;  // If negative, array reference is illegal

    private static readonly MethodInfo getArrayViewMethod
      = typeof(CGNormalCellArea).GetMethod("GetArrayView");
    private static readonly ValueTable<ArrayView> valueTable
      = new ValueTable<ArrayView>();

    public CGNormalCellArea(ArrayView array) {
      if (array.sheet.IsFunctionSheet)
        this.index = -1;              // Illegal cell area
      else
        this.index = valueTable.GetIndex(array);
    }

    public override void Compile() {
      if (index >= 0) {
        ilg.Emit(OpCodes.Ldc_I4, index);
        ilg.Emit(OpCodes.Call, getArrayViewMethod);
      } else
        LoadErrorValue(ErrorValue.Make("#FUNERR: Range on function sheet"));
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      return this;
    }

    public override void CompileToDoubleOrNan() {
      GenLoadErrorNan(ErrorValue.argTypeError);
    }

    // Used only (through reflection and) from generated code 
    public static ArrayView GetArrayView(int number) {
      return valueTable[number];
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) { }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) {
      // We do not track dependencies on cells or areas on ordinary sheets
    }

    public override Typ Type() {
      return Typ.Array;
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) { }
  }

  // ----------------------------------------------------------------

  abstract public class CGArithmetic1 : CGStrictOperation {
    public CGArithmetic1(CGExpr[] es, Applier applier) : base(es, applier) { }

    public override void Compile() {
      CompileToDoubleOrNan();
      WrapDoubleToNumberValue();
    }

    public override Typ Type() { return Typ.Number; }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Number;
    }

    public override int Arity { get { return 1; } }
  }

  // ----------------------------------------------------------------

  public class CGNot : CGArithmetic1 {
    private static readonly Applier notApplier = Function.Get("NOT").Applier;

    public CGNot(CGExpr[] es) : base(es, notApplier) { }

    public override void CompileToDoubleOrNan() {
      // sestoft: Seems a bit redundant?
      CompileToDoubleProper(
        new Gen(delegate { }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (es.Length != Arity) {
        SetArgCountErrorNan();
        ifOther.Generate(ilg);
      } else
        es[0].CompileToDoubleProper(
            new Gen(delegate {
              ilg.Emit(OpCodes.Ldc_R8, 0.0);
              ilg.Emit(OpCodes.Ceq);
              ilg.Emit(OpCodes.Conv_R8);
              ifProper.Generate(ilg);
            }),
            ifOther);
    }

    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      es[0].CompileCondition(ifFalse, ifTrue, ifOther);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGNot(res);
    }

    public override string ToString() {
      return FormatAsCall("NOT");
    }
  }

  // ----------------------------------------------------------------

  public class CGNeg : CGArithmetic1 {
    private static readonly Applier negApplier = Function.Get("NEG").Applier;

    public CGNeg(CGExpr[] es) : base(es, negApplier) { }

    public override void CompileToDoubleOrNan() {
      es[0].CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Neg);
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (es.Length != Arity) {
        SetArgCountErrorNan();
        ifOther.Generate(ilg);
      } else
        es[0].CompileToDoubleProper(
            new Gen(delegate {
              ilg.Emit(OpCodes.Neg);
              ifProper.Generate(ilg);
            }),
            ifOther);
    }

    // -x is true/false/infinite/NaN if and only if x is:
    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      es[0].CompileCondition(ifTrue, ifFalse, ifOther);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGNeg(res);
    }

    public override string ToString() {
      return "-" + es[0];
    }
  }

  // ----------------------------------------------------------------

  public class CGArithmetic2 : CGStrictOperation {
    public readonly OpCode opCode;
    public readonly String op;

    public CGArithmetic2(OpCode opCode, String op, CGExpr[] es)
      : base(es, Function.Get(op).Applier) {
      this.opCode = opCode;
      this.op = op;
    }

    // Reductions such as 0*e==>0 are a bit dubious when you 
    // consider that e could evaluate to ArgType error or similar:
    public CGExpr Make(CGExpr[] es) {
      if (es.Length == 2) {
        if (op == "+" && es[0].Is(0))
          return es[1]; // 0+e = e
        else if ((op == "+" || op == "-") && es[1].Is(0))
          return es[0]; // e+0 = e-0 = e
        else if (op == "-" && es[0].Is(0))
          return new CGNeg(new CGExpr[] { es[1] }); // 0-e = -e
        else if (op == "*" && (es[0].Is(0) || es[1].Is(0)))
          return new CGNumberConst(NumberValue.ZERO); // 0*e = e*0 = 0 (**)
        else if (op == "*" && es[0].Is(1)) 
          return es[1]; // 1*e = e
        else if ((op == "*" || op == "/") && es[1].Is(1))
          return es[0]; // e*1 = e/1 = e
        else if (op == "^" && (es[0].Is(1) || es[1].Is(1)))
          return es[0]; // e^1 = e and also 1^e = 1 (IEEE)
        else if (op == "^" && es[1].Is(0))
          return new CGNumberConst(NumberValue.ONE); // e^0 = 1 (IEEE)
      }
      return new CGArithmetic2(opCode, op, es);
    }

    public override void Compile() {
      CompileToDoubleOrNan();
      WrapDoubleToNumberValue();
    }

    public override void CompileToDoubleOrNan() {
      es[0].CompileToDoubleOrNan();
      es[1].CompileToDoubleOrNan();
      ilg.Emit(opCode);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return Make(res);
//      return new CGArithmetic2(opCode, op, res);
    }

    public override Typ Type() {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Number;
    }

    public override int Arity { get { return 2; } }

    public override string ToString() {
      if (es.Length == 2)
        return "(" + es[0] + op + es[1] + ")";
      else
        return "Err: ArgCount";
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A comparison takes two operands, both numeric for now, and produces
  /// the outcome true (1.0) or false (0.0), or in case either operand 
  /// evaluates to NaN or +/-infinity, it produces that NaN or a plain NaN.
  /// </summary>
  public abstract class CGComparison : CGStrictOperation {
    public CGComparison(CGExpr[] es, Applier applier) : base(es, applier) { }

    // These are used in a template method pattern
    // Emit instruction to compare doubles, leaving 1 on stack if true:
    protected abstract void GenCompareDouble();
    // Emit instructions to compare doubles, jump to target if false:
    protected abstract void GenDoubleFalseJump(Label target);
    protected abstract String Name { get; }

    public override void Compile() {
      CompileToDoubleProper(new Gen(delegate { WrapDoubleToNumberValue(); }),
                            GenLoadTestDoubleErrorValue());
    }

    // A comparison always evaluates to a double; if it cannot evaluate to
    // a proper double, it evaluates to an infinity or NaN.
    // This code seems a bit redundant: It leaves the double on the stack top,
    // whether or not it is proper?  On the other hand, it can be improved only
    // by basically duplicating the CompileToDoubleProper method's body.  
    // Doing so might avoid a jump to a jump or similar, though.
    public override void CompileToDoubleOrNan() {
      CompileToDoubleProper(
        new Gen(delegate { }),
        new Gen(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    // A comparison evaluates to a proper double only if both operands do
    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      es[0].CompileToDoubleProper(
        new Gen(delegate {
          es[1].CompileToDoubleProper(
             new Gen(delegate {
               GenCompareDouble();
               ilg.Emit(OpCodes.Conv_R8);
               ifProper.Generate(ilg);
             }),
             new Gen(delegate {
               ilg.Emit(OpCodes.Pop);
               ifOther.Generate(ilg);
             }));
        }),
        ifOther);
    }

    // This override combines the ordering predicate and the conditional jump
    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      es[0].CompileToDoubleProper(
        new Gen(delegate {
          es[1].CompileToDoubleProper(
            new Gen(delegate {
              GenDoubleFalseJump(ifFalse.GetLabel(ilg));
              ifTrue.Generate(ilg);
              if (!ifFalse.Generated) {
                Label endLabel = ilg.DefineLabel();
                ilg.Emit(OpCodes.Br, endLabel);
                ifFalse.Generate(ilg);
                ilg.MarkLabel(endLabel);
              }
            }),
            new Gen(delegate {
              ilg.Emit(OpCodes.Pop);
              ifOther.Generate(ilg);
            }));
        }),
        ifOther);
    }

    public override string ToString() {
      return es[0] + Name + es[1];
    }

    public override Typ Type() {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Number;
    }

    public override int Arity { get { return 2; } }
  }

  // ----------------------------------------------------------------

  // Comparisons should also be made to work for strings etc.  Static type
  // information should be exploited when available, and when not, 
  // a general comparer should be generated.  For doubles, we should
  // check that we're consistent with respect to unordered comparison.
  public class CGGreaterThan : CGComparison {
    private static readonly Applier gtApplier = Function.Get(">").Applier;

    public CGGreaterThan(CGExpr[] es) : base(es, gtApplier) { }

    protected override void GenCompareDouble() {
      ilg.Emit(OpCodes.Cgt);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Ble, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGGreaterThan(res);
    }

    protected override string Name { get { return ">"; } }
  }

  // ----------------------------------------------------------------

  public class CGLessThan : CGComparison {
    private static readonly Applier ltApplier = Function.Get("<").Applier;

    public CGLessThan(CGExpr[] es) : base(es, ltApplier) { }

    protected override void GenCompareDouble() {
      ilg.Emit(OpCodes.Clt);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Bge, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGLessThan(res);
    }

    protected override string Name { get { return "<"; } }
  }

  // ----------------------------------------------------------------

  public class CGLessThanOrEqual : CGComparison {
    private static readonly Applier leApplier = Function.Get("<=").Applier;

    public CGLessThanOrEqual(CGExpr[] es) : base(es, leApplier) { }

    protected override void GenCompareDouble() {
      // OpCodes.Not negates all int bits and doesn't work here!
      ilg.Emit(OpCodes.Cgt);
      ilg.Emit(OpCodes.Ldc_I4_0);
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Bgt, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGLessThanOrEqual(res);
    }

    protected override string Name { get { return "<="; } }
  }

  // ----------------------------------------------------------------

  public class CGGreaterThanOrEqual : CGComparison {
    private static readonly Applier geApplier = Function.Get(">=").Applier;

    public CGGreaterThanOrEqual(CGExpr[] es) : base(es, geApplier) { }

    protected override void GenCompareDouble() {
      ilg.Emit(OpCodes.Clt);
      ilg.Emit(OpCodes.Ldc_I4_0);
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Blt, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGGreaterThanOrEqual(res);
    }

    protected override string Name { get { return ">="; } }
  }

  // ----------------------------------------------------------------

  public class CGEqual : CGComparison {
    private static readonly Applier eqApplier = Function.Get("=").Applier;

    public CGEqual(CGExpr[] es) : base(es, eqApplier) { }

    public static CGExpr Make(CGExpr[] es) {
      if (es.Length == 2) {
        if (es[0].Is(0))        // 0.0=e1 ==> NOT(e1)
          return new CGNot(new CGExpr[] { es[1] });
        else if (es[1].Is(0))   // e0=0.0 ==> NOT(e0)
          return new CGNot(new CGExpr[] { es[0] });
      }
      return new CGEqual(es);
    }

    protected override void GenCompareDouble() {
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Bne_Un, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return Make(res);
    }

    protected override string Name { get { return "="; } }
  }

  // ----------------------------------------------------------------

  public class CGNotEqual : CGComparison {
    private CGNotEqual(CGExpr[] es) : base(es, Function.Get("<>").Applier) { }

    public static CGExpr Make(CGExpr[] es) {
      if (es.Length == 2) {
        if (es[0].Is(0))        // 0.0<>e1 ==> AND(e1)
          return new CGAnd(new CGExpr[] { es[1] });
        else if (es[1].Is(0))   // e0<>0.0 ==> AND(e0)
          return new CGAnd(new CGExpr[] { es[0] });
      }
      return new CGNotEqual(es);
    }

    protected override void GenCompareDouble() {
      ilg.Emit(OpCodes.Ceq);
      ilg.Emit(OpCodes.Ldc_I4_0);
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target) {
      ilg.Emit(OpCodes.Beq, target);
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return Make(res);
    }

    protected override string Name { get { return "<>"; } }
  }

  // ----------------------------------------------------------------

  public abstract class CGConst : CGExpr {
    public static CGConst Make(double d) {
      return Make(NumberValue.Make(d));
    }

    public static CGConst Make(Value v) {
      if (v is NumberValue)
        return new CGNumberConst((v as NumberValue));
      else if (v is TextValue)
        return new CGTextConst(v as TextValue);
      else if (v is ErrorValue)
        return new CGError((v as ErrorValue));
      else
        return new CGValueConst(v);
    }

    public abstract Value Value { get; }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      return this;
    }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) { }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) { }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) { }
  }

  // ----------------------------------------------------------------

  public class CGTextConst : CGConst {
    public readonly TextValue value;
    private readonly int index;

    public CGTextConst(TextValue value) {
      this.value = value;
      this.index = TextValue.GetIndex(value.value);
    }

    public override void Compile() {
      if (value.value == "")
        ilg.Emit(OpCodes.Ldsfld, TextValue.emptyField);
      else {
        ilg.Emit(OpCodes.Ldc_I4, index);
        ilg.Emit(OpCodes.Call, TextValue.fromIndexMethod);
      }
    }

    public override void CompileToDoubleOrNan() {
      LoadErrorNan(ErrorValue.argTypeError);
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      ifOther.Generate(ilg);
    }

    // In Excel, a text used in a conditional produces the error #VALUE! -- mostly
    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      ifOther.Generate(ilg);
    }

    public override Value Value {
      get { return value; }
    }

    public override Typ Type() {
      return Typ.Text;
    }
  }

  // ----------------------------------------------------------------

  public class CGNumberConst : CGConst {
    public readonly NumberValue number;

    public CGNumberConst(NumberValue number) {
      this.number = number;
    }

    public override void Compile() {
      // sestoft: Would be better to load the NumberValue from this instance, 
      // but then it would need to be stored somewhere (e.g. in a global Value
      // array from which it can be loaded).  See notes.txt.
      if (number.value == 0.0)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField);
      else if (number.value == 1.0)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField);
      else if (number.value == Math.PI)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.piField);
      else {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        WrapDoubleToNumberValue();
      }
    }

    public override void CompileToDoubleOrNan() {
      ilg.Emit(OpCodes.Ldc_R8, number.value);
    }

    public override void CompileToDoubleProper(Gen ifProper, Gen ifOther) {
      if (double.IsInfinity(number.value) || double.IsNaN(number.value)) {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ilg.Emit(OpCodes.Stloc, testDouble);
        ifOther.Generate(ilg);
      } else {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ifProper.Generate(ilg);
      }
    }

    public override void CompileCondition(Gen ifTrue, Gen ifFalse, Gen ifOther) {
      if (Double.IsInfinity(number.value) || Double.IsNaN(number.value)) {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ilg.Emit(OpCodes.Stloc, testDouble);
        ifOther.Generate(ilg);
      } else if (number.value != 0)
        ifTrue.Generate(ilg);
      else
        ifFalse.Generate(ilg);
    }

    public override Value Value {
      get { return number; }
    }

    public override string ToString() {
      return number.value.ToString();
    }

    public override Typ Type() {
      return Typ.Number;
    }
  }

  // ----------------------------------------------------------------

  public class CGError : CGConst {
    private readonly ErrorValue errorValue;

    public CGError(ErrorValue errorValue) {
      this.errorValue = errorValue;
    }

    public CGError(String message) : this(ErrorValue.Make(message))
    { }

    // This is used to implement the ERR function
    public CGError(CGExpr[] es) {
      if (es.Length != 1)
        errorValue = ErrorValue.argCountError;
      else {
        CGTextConst messageConst = es[0] as CGTextConst;
        if (messageConst == null)
          errorValue = ErrorValue.argTypeError;
        else
          errorValue = ErrorValue.Make("#ERR: " + messageConst.value.value);
      }
    }

    public override void Compile() {
      LoadErrorValue(errorValue);
    }

    public override void CompileToDoubleOrNan() {
      ilg.Emit(OpCodes.Ldc_R8, errorValue.ErrorNan);
    }

    public override Value Value {
      get { return errorValue; }
    }

    public override string ToString() {
      return errorValue.message;
    }

    public override Typ Type() {
      return Typ.Error;
    }
  }

  // ----------------------------------------------------------------

  public class CGValueConst : CGConst {
    public readonly int index;

    private static readonly MethodInfo loadValueConstMethod
      = typeof(CGValueConst).GetMethod("LoadValueConst");
    private static readonly ValueTable<Value> valueTable
      = new ValueTable<Value>();

    public CGValueConst(Value value) {
      this.index = valueTable.GetIndex(value);
    }

    public override void Compile() {
      ilg.Emit(OpCodes.Ldc_I4, index);
      ilg.Emit(OpCodes.Call, loadValueConstMethod);
    }

    public override void CompileToDoubleOrNan() {
      LoadErrorNan(ErrorValue.argTypeError);
    }

    // Used only (through reflection and) from generated code 
    public static Value LoadValueConst(int index) {
      return valueTable[index];
    }

    public override Value Value {
      get { return valueTable[index]; }
    }

    public override string ToString() {
      return String.Format("CGValueConst({0}) at {1}", valueTable[index], index);
    }

    public override Typ Type() {
      return Typ.Value;
    }
  }

  // ----------------------------------------------------------------

  public class CGClosure : CGStrictOperation {
    private static readonly Applier closureApplier = Function.Get("CLOSURE").Applier;

    public CGClosure(CGExpr[] es)
      : base(es, closureApplier) { }

    public override void Compile() {
      int argCount = es.Length - 1;
      if (es.Length < 1) 
        LoadErrorValue(ErrorValue.argCountError);
      else if (es[0] is CGTextConst) {
        String name = (es[0] as CGTextConst).value.value;
        SdfInfo sdfInfo = SdfManager.GetInfo(name);
        if (sdfInfo == null) 
          LoadErrorValue(ErrorValue.nameError);
        else if (argCount != 0 && argCount != sdfInfo.arity)
          LoadErrorValue(ErrorValue.argCountError);
        else {
          ilg.Emit(OpCodes.Ldc_I4, sdfInfo.index);
          CompileToValueArray(argCount, 1, es);
          ilg.Emit(OpCodes.Call, FunctionValue.makeMethod);
        }
      } else {
        es[0].Compile();
        CheckType(FunctionValue.type,
          new Gen(delegate { 
            CompileToValueArray(argCount, 1, es);
            ilg.Emit(OpCodes.Call, FunctionValue.furtherApplyMethod);
          }),
          GenLoadErrorValue(ErrorValue.argTypeError));
      }
    }

    public override CGExpr Residualize(CGExpr[] res) {
      return new CGClosure(res);
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type() {
      return Typ.Function;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos) {
      return Typ.Value;
    }

    public override void CompileToDoubleOrNan() {
      // TODO: Is this right?
      LoadErrorNan(ErrorValue.argTypeError);
    }
  }

  // ----------------------------------------------------------------

  /// <summary>
  /// Reference to a cell that must be evaluated at runtime; namely, a cell
  /// in an ordinary sheet, not inside a function sheet.
  /// </summary>
  public class CGNormalCellRef : CGExpr {
    private readonly int index; // If negative, cell ref is illegal

    private static readonly MethodInfo getAddressMethod
      = typeof(CGNormalCellRef).GetMethod("GetAddress");
    private static readonly ValueTable<FullCellAddr> valueTable
      = new ValueTable<FullCellAddr>();

    public CGNormalCellRef(FullCellAddr cellAddr) {
      if (cellAddr.sheet.IsFunctionSheet)
        this.index = -1;             // Illegal cell reference
      else
        this.index = valueTable.GetIndex(cellAddr);
    }

    public override void Compile() {
      if (index >= 0) {
        ilg.Emit(OpCodes.Ldc_I4, index);
        ilg.Emit(OpCodes.Call, getAddressMethod);
        // HERE
        ilg.Emit(OpCodes.Stloc, tmpFullCellAddr);
        ilg.Emit(OpCodes.Ldloca, tmpFullCellAddr);
        ilg.Emit(OpCodes.Call, FullCellAddr.evalMethod);
      } else
        LoadErrorValue(ErrorValue.Make("#FUNERR: Ref to other function sheet"));
    }

    public override CGExpr PEval(PEnv pEnv, bool hasDynamicControl) {
      return this;
    }

    public static FullCellAddr GetAddress(int number) {
      return valueTable[number];
    }

    public override void CountUses(Typ typ, HashBag<FullCellAddr> numberUses) { }

    public override void EvalCond(PathCond evalCond, IDictionary<FullCellAddr, PathCond> evalConds,
                                  List<CGCachedExpr> caches) { }

    public override void DependsOn(FullCellAddr here, Action<FullCellAddr> dependsOn) {
      // We do not track dependencies on cells on ordinary sheets
    }

    public override Typ Type() {
      // We don't infer cell types in ordinary sheets, so assume the worst
      return Typ.Value;
    }

    public override void CompileToDoubleOrNan() {
      Compile();
      UnwrapToDoubleOrNan();
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A Gen object is a code generator that avoids generating the same code 
  /// multiple times, and also to some extent avoids generating jumps to jumps.  
  /// </summary>

  public class Gen {
    private readonly Action generate;
    private Label? label;
    private bool generated;  // Invariant: generated implies label.HasValue

    public Gen(Action generate) {
      this.generate = generate;
      label = null;
      generated = false;
    }

    // A Generate object has a unique label
    public Label GetLabel(ILGenerator ilg) {
      if (!label.HasValue)
        label = ilg.DefineLabel();
      return label.Value;
    }

    public bool Generated { get { return generated; } }

    public void Generate(ILGenerator ilg) {
      if (generated)
        ilg.Emit(OpCodes.Br, GetLabel(ilg));
      else {
        ilg.MarkLabel(GetLabel(ilg));
        generated = true;
        generate();
      }
    }
  }

  /// <summary>
  /// An environment for partial evaluation
  /// </summary>

  public class PEnv : Dictionary<FullCellAddr, CGExpr> { }

  // ----------------------------------------------------------------

  /// <summary>
  /// Table of built-in functions: signature, MethodInfo object, ...
  /// </summary>

  public class FunctionInfo {
    public readonly String name;             // For lookup and display
    public readonly MethodInfo methodInfo;   // For code generation
    public readonly Signature signature;     // For arg. compilation
    public readonly bool isSerious;          // Cache it or not?
    public readonly Applier applier;         // For specialization
 
    private static readonly IDictionary<String, FunctionInfo>
      functions = new Dictionary<String, FunctionInfo>();

    // Some signatures for fixed-arity built-in functions; return type first
    private static readonly Signature
      numToNum = new Signature(Typ.Number, Typ.Number),
      numNumToNum = new Signature(Typ.Number, Typ.Number, Typ.Number),
      unitToNum = new Signature(Typ.Number),
      valuesToNum = new Signature(Typ.Number, null),  // Variable arity
      valuesToValue = new Signature(Typ.Value, null),  // Variable arity
      valueToNum = new Signature(Typ.Number, Typ.Value),
      valueNumNumToValue = new Signature(Typ.Value, Typ.Value, Typ.Number, Typ.Number),
      valueValueToNum = new Signature(Typ.Number, Typ.Value, Typ.Value),
      valueToValue = new Signature(Typ.Value, Typ.Value),
      valueValueToValue = new Signature(Typ.Value, Typ.Value, Typ.Value),
      valueValueValueToValue = new Signature(Typ.Value, Typ.Value, Typ.Value, Typ.Value),
      valueNumNumNumNumToValue = new Signature(Typ.Value, Typ.Value, Typ.Number, Typ.Number, Typ.Number, Typ.Number);

    // Fixed-arity built-in functions that do not require special treatment.
    // Those that require special treatment are in CGComposite.Make.
    static FunctionInfo() {
      MakeMath1("Abs");
      MakeMath1("Acos");
      MakeMath1("Asin");
      MakeMath1("Atan");
      MakeFunction("ATAN2", "ExcelAtan2", numNumToNum, false);
      MakeFunction("AVERAGE", "Average", valuesToNum, true);
      MakeFunction("BENCHMARK", "Benchmark", valueValueToValue, true);
      MakeFunction("CEILING", "ExcelCeiling", numNumToNum, false);
      MakeFunction("COLMAP", "ColMap", valueValueToValue, true);
      MakeFunction("COLUMNS", "Columns", valueToNum, false);
      MakeFunction("&", "ExcelConcat", valueValueToValue, false);
      MakeFunction("CONSTARRAY", "ConstArray", valueValueValueToValue, true);
      MakeMath1("Cos");
      MakeFunction("COUNTIF", "CountIf", valueValueToValue, true);
      MakeFunction("EQUAL", "Equal", valueValueToNum, false);
      MakeMath1("Exp");
      MakeFunction("FLOOR", "ExcelFloor", numNumToNum, false);
      MakeFunction("HARRAY", "HArray", valuesToValue, true);
      MakeFunction("HCAT", "HCat", valuesToValue, true);
      MakeFunction("HSCAN", "HScan", valueValueValueToValue, true);
      MakeFunction("INDEX", "Index", valueNumNumToValue, false);
      MakeFunction("ISARRAY", "IsArray", valueToNum, false);
      MakeFunction("ISERROR", "IsError", valueToNum, false);
      MakeMath1("LN", "Log");
      MakeMath1("LOG", "Log10");
      MakeMath1("LOG10", "Log10");
      MakeFunction("MAP", "Map", valuesToValue, true);
      MakeFunction("MAX", "Max", valuesToNum, true);
      MakeFunction("MIN", "Min", valuesToNum, true);
      MakeFunction("MOD", "ExcelMod", numNumToNum, false);
      MakeFunction("NOW", "ExcelNow", unitToNum, false);
      MakeFunction("^", "ExcelPow", numNumToNum, false);
      MakeFunction("RAND", "ExcelRand", unitToNum, false);
      MakeFunction("REDUCE", "Reduce", valueValueValueToValue, true);
      MakeFunction("ROUND", "ExcelRound", numNumToNum, false);
      MakeFunction("ROWMAP", "RowMap", valueValueToValue, true);
      MakeFunction("ROWS", "Rows", valueToNum, false);
      MakeFunction("SIGN", "Sign", numToNum, false);
      MakeMath1("Sin");
      MakeFunction("SLICE", "Slice", valueNumNumNumNumToValue, false);
      MakeFunction("SPECIALIZE", "Specialize", valueToValue, true);
      MakeMath1("Sqrt");
      MakeFunction("SUM", "Sum", valuesToNum, true);
      MakeFunction("SUMIF", "SumIf", valueValueToValue, true);
      MakeFunction("TABULATE", "Tabulate", valueValueValueToValue, true);
      MakeMath1("Tan");
      MakeFunction("TRANSPOSE", "Transpose", valueToValue, true);
      MakeFunction("VARRAY", "VArray", valuesToValue, true);
      MakeFunction("VCAT", "VCat", valuesToValue, true);
      MakeFunction("VSCAN", "VScan", valueValueValueToValue, true);
    }

    public FunctionInfo(String name, MethodInfo methodInfo, Applier applier, Signature signature, bool isSerious) {
      this.name = name.ToUpper();
      this.methodInfo = methodInfo;
      this.applier = applier;
      this.signature = signature;
      functions.Add(this.name, this);
      this.isSerious = isSerious;
    }

    private static FunctionInfo MakeMath1(String funcalcName, String mathName) {
      return new FunctionInfo(funcalcName.ToUpper(),
        typeof(Math).GetMethod(mathName, new Type[] { typeof(double) }),
        Function.Get(funcalcName.ToUpper()).Applier,
        numToNum,
        isSerious: false);
    }

    private static FunctionInfo MakeMath1(String name) {
      return MakeMath1(name, name);
    }

    private static FunctionInfo MakeFunction(String ssName, MethodInfo method,
                                             Signature signature, bool isSerious) {
      return new FunctionInfo(ssName,
        method,
        Function.Get(ssName.ToUpper()).Applier,
        signature,
        isSerious);
    }

    private static FunctionInfo MakeFunction(String ssName, String implementationName,
                                             Signature signature, bool isSerious) {
      return new FunctionInfo(ssName,
        Function.type.GetMethod(implementationName),
        Function.Get(ssName.ToUpper()).Applier,
        signature,
        isSerious);
    }

    public static bool Find(String name, out FunctionInfo functionInfo) {
      return functions.TryGetValue(name, out functionInfo);
    }
  }

  // -----------------------------

  public class Signature {
    public readonly Typ retType;
    public readonly Typ[] argTypes;   // null means variadic

    public Signature(Typ retType, params Typ[] argTypes) {
      this.retType = retType;
      this.argTypes = argTypes;
    }

    public int Arity {
      get { return argTypes != null ? argTypes.Length : -1; }
    }
  }
}